package com.oms.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.oms.common.ResponseCodeConst;
import com.oms.common.ResponseDTO;
import com.oms.module.admin.constant.AdminResponseCodeConstant;
import com.oms.module.admin.domain.entity.AdminEntity;
import com.oms.module.common.domain.MenuEntity;
import com.oms.module.common.domain.RoleEntity;
import com.oms.module.common.service.MenuService;
import com.oms.module.common.service.UserLoginService;
import com.oms.module.merchant.domain.entity.UserEntity;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * @Author zhanghailong QQ38592077
 * @Description TODO
 * @Date 2020/6/2 14:43
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserLoginService userLoginService;
    @Autowired
    MyAccessDecisionManager myAccessDecisionManager;
    @Autowired
    MyFilter myFilter;

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userLoginService);
        super.configure(auth);
    }


    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/css/**", "/js/**", "/index.html", "/img/**", "/fonts/**", "/favicon.ico", "/verifyCode",
                "/v2/api-docs", "/swagger-resources/configuration/ui",
                "/swagger-resources", "/swagger-resources/configuration/security",
                "/swagger-ui.html", "/webjars/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @SneakyThrows
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setAccessDecisionManager(myAccessDecisionManager);
                        object.setSecurityMetadataSource(myFilter);
                        return object;
                    }
                })
                .and()
                .formLogin()
                .usernameParameter("username").passwordParameter("password")
                .loginProcessingUrl("/doLogin")
                .loginPage("/login")
//                登陆成功回调
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter writer = resp.getWriter();
                        Object principal = authentication.getPrincipal();
                        String s = "";
                        if (principal instanceof AdminEntity) {
                            AdminEntity adminEntity = (AdminEntity) principal;
                            adminEntity.setPassword(null);
                            s = new ObjectMapper().writeValueAsString(ResponseDTO.succData(adminEntity));
                        } else if (principal instanceof UserEntity) {
                            UserEntity adminEntity = (UserEntity) principal;
                            adminEntity.setPassword(null);
                            s = new ObjectMapper().writeValueAsString(ResponseDTO.succData(adminEntity));
                        }
                        writer.write(s);
                        writer.flush();
                        writer.close();
                    }
                })
//                登陆失败回调
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter writer = resp.getWriter();
                        String s = "";
                        if (e instanceof LockedException) {
                            s = new ObjectMapper().writeValueAsString(ResponseDTO.wrap(AdminResponseCodeConstant.ACCOUNT_LOCK));
                        } else if (e instanceof CredentialsExpiredException) {
                            s = new ObjectMapper().writeValueAsString(ResponseDTO.wrap(AdminResponseCodeConstant.PASSWORD_EXPIRED));
                        } else if (e instanceof AccountExpiredException) {
                            s = new ObjectMapper().writeValueAsString(ResponseDTO.wrap(AdminResponseCodeConstant.ACCOUNT_EXPIRED));
                        } else if (e instanceof DisabledException) {
                            s = new ObjectMapper().writeValueAsString(ResponseDTO.wrap(AdminResponseCodeConstant.DISABLED));
                        } else if (e instanceof BadCredentialsException) {
                            s = new ObjectMapper().writeValueAsString(ResponseDTO.wrap(ResponseCodeConst.LOFING_FAIL_ACCOUNT));
                        } else {
                            s = new ObjectMapper().writeValueAsString(ResponseDTO.wrap(AdminResponseCodeConstant.LANDING_FAILURE));
                        }
                        writer.write(s);
                        writer.flush();
                        writer.close();
                    }
                })
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    @Override
                    public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter writer = resp.getWriter();
                        writer.write(new ObjectMapper().writeValueAsString(ResponseDTO.succMsg("注销成功！")));
                        writer.flush();
                        writer.close();
                    }
                })
                .permitAll()
                .and()
                .rememberMe()
                .key("OMS")
                .rememberMeParameter("rememberMe")
                .and()
                .csrf()
                .ignoringAntMatchers("/user/authenticationInvitationCode", "/user/getVerificationCode", "/user/reg", "/login", "/doLogin", "/logout")
                .csrfTokenRepository(cookieCsrfTokenRepository())
                .and()
//                未登录处理异常
                .exceptionHandling()
                .authenticationEntryPoint(new AuthenticationEntryPoint() {
                    @Override
                    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
                        response.setContentType("application/json;charset=utf-8");
                        PrintWriter writer = response.getWriter();
                        if (authException instanceof InsufficientAuthenticationException) {
                            writer.write(new ObjectMapper().writeValueAsString(ResponseDTO.wrap(ResponseCodeConst.LOGIN)));
                        }
                        writer.flush();
                        writer.close();
                    }
                })
                .and()
                .sessionManagement()
                .maximumSessions(1)

        ;
        //json登陆配置
        http.addFilterAfter(myUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }


    @Bean
    MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter() throws Exception {
        MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter = new MyUsernamePasswordAuthenticationFilter();
        myUsernamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManager());
        return myUsernamePasswordAuthenticationFilter;
    }

    @Bean
    CookieCsrfTokenRepository cookieCsrfTokenRepository() {
        CookieCsrfTokenRepository cookieCsrfTokenRepository = new CookieCsrfTokenRepository();
        cookieCsrfTokenRepository.setCookieHttpOnly(false);
        cookieCsrfTokenRepository.setCookiePath("/");
        return cookieCsrfTokenRepository;
    }

    /**
     * 兼容json登陆
     */
    class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
            if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
                if (!request.getMethod().equals("POST")) {
                    throw new AuthenticationServiceException(
                            "Authentication method not supported: " + request.getMethod());
                }
                String username = null;
                String password = null;
                try {
                    Map<String, String> map = new ObjectMapper().readValue(request.getInputStream(), Map.class);
                    username = map.get("username");
                    password = map.get("password");
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (username == null) {
                    username = "";
                }
                if (password == null) {
                    password = "";
                }
                username = username.trim();
                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                        username, password);
                // Allow subclasses to set the "details" property
                setDetails(request, authRequest);
                return this.getAuthenticationManager().authenticate(authRequest);
            }
            return super.attemptAuthentication(request, response);
        }
    }

    /**
     * 获取当前访问路径需要的角色
     */
    @Component
    class MyFilter implements FilterInvocationSecurityMetadataSource {

        AntPathMatcher antPathMatcher = new AntPathMatcher();
        @Autowired
        MenuService menuService;
        final String PERMIT_ALL = "/chat/**";

        @Override
        public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
            String url = ((FilterInvocation) object).getRequestUrl();
//            if (antPathMatcher.match(PERMIT_ALL, url)) {
//                return org.springframework.security.access.SecurityConfig.createList("ROLE_permitAll");
//            }
//            查询所有菜单
            List<MenuEntity> list = menuService.selectAll();
            for (MenuEntity menuEntity : list) {
//                如果菜单路劲匹配 则获取访问该菜单所需权限
                if (antPathMatcher.match(menuEntity.getUrl(), url)) {
                    List<RoleEntity> roles = menuEntity.getRoles();
                    String[] strings = new String[roles.size()];
                    for (int i = 0; i < roles.size(); i++) {
                        strings[i] = roles.get(i).getName();
                    }
                    return org.springframework.security.access.SecurityConfig.createList(strings);
                }
            }
//            如果路径全部不匹配 则默认登陆就可以访问 并给登陆标识
            return org.springframework.security.access.SecurityConfig.createList("ROLE_login");
        }

        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            return null;
        }

        @Override
        public boolean supports(Class<?> aClass) {
            return false;
        }
    }

    /**
     * 角色路径匹配
     */
    @Component
    class MyAccessDecisionManager implements AccessDecisionManager {

        @Override
        public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
            for (ConfigAttribute config : collection) {
                if ("ROLE_permitAll".equals(config.getAttribute())) {
                    return;
                }
//                如果只需登陆权限 则去判断是否登陆
                if ("ROLE_login".equals(config.getAttribute())) {
                    if (authentication instanceof AnonymousAuthenticationToken) {
                        throw new AccessDeniedException("未登录请登陆");
                    } else {
                        return;
                    }
                }
//                获取当前用户所拥有的权限 跟当前路径所需要的权限进行匹配
                Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
                for (GrantedAuthority grantedAuthority : authorities) {
                    if (grantedAuthority.getAuthority().equals(config.getAttribute())) {
                        return;
                    }
                }
            }
//            如果都没匹配到 证明是非法登陆
            throw new AccessDeniedException("非法访问！");
        }

        @Override
        public boolean supports(ConfigAttribute configAttribute) {
            return true;
        }

        @Override
        public boolean supports(Class<?> aClass) {
            return true;
        }
    }

}
